/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wpy.auditlog.param;

import com.wpy.auditlog.config.AuditLogConfig;
import com.wpy.auditlog.enums.OperationResult;
import com.wpy.auditlog.enums.OperationType;
import com.wpy.auditlog.enums.SourceChannel;
import com.wpy.auditlog.properties.AuditProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Enumeration;
import java.util.concurrent.CompletableFuture;

public class AuditParam {

    private String companyCode;
    private String appId;
    private String functionModule;
    private String operationTime;
    private String operationUser;
    private String operationUserType;
    private String operationUserid;
    private String userid;
    private SourceChannel sourceChannel;
    private String srcIp;
    private String serverIp;
    private OperationType operationType;
    private Object operationObject;
    private OperationResult operationResult;
    private String failReason;
    private Integer recordCounts;
    private String recordType;
    private String recordUrl;
    private Integer recordPage;
    private Integer recordPageCounts;

    public AuditParam() {
    }

    private AuditParam(AuditBuilder builder) {
        this.companyCode = builder.companyCode;
        this.appId = builder.appId;
        this.functionModule = builder.functionModule;
        this.operationTime = builder.operationTime;
        this.operationUser = builder.operationUser;
        this.operationUserType = builder.operationUserType;
        this.operationUserid = builder.operationUserid;
        this.userid = builder.userid;
        this.sourceChannel = builder.sourceChannel;
        this.srcIp = builder.srcIp;
        this.serverIp = builder.serverIp;
        this.operationType = builder.operationType;
        this.operationObject = builder.operationObject;
        this.operationResult = builder.operationResult;
        this.failReason = builder.failReason;
        this.recordCounts = builder.recordCounts;
        this.recordType = builder.recordType;
        this.recordUrl = builder.recordUrl;
        this.recordPage = builder.recordPage;
        this.recordPageCounts = builder.recordPageCounts;
    }

    private static final Logger logger = LoggerFactory.getLogger(AuditParam.class);
    private static AuditProperties auditProperties;
    private static AuditLogConfig auditLogConfig;

    protected static void initAuditProperties(AuditProperties properties) {
        AuditParam.auditProperties = properties;
    }

    protected static void initAuditLogConfig(AuditLogConfig config) {
        AuditParam.auditLogConfig = config;
    }

    public static AuditBuilder builder() {
        if (auditLogConfig == null || !auditLogConfig.isAuditLogEnabled()) {
            logger.warn("无法(同步)初始化AuditBuilder: AuditLog is not enabled");
            return new AuditBuilder();
        }
        return fillAuditBuilder((ServletRequestAttributes) RequestContextHolder.getRequestAttributes(), new AuditBuilder());
    }

    public static AuditBuilder builderAsync() {
        if (auditLogConfig == null || !auditLogConfig.isAuditLogEnabled()) {
            logger.warn("无法(异步)初始化AuditBuilder: AuditLog is not enabled");
            return new AuditBuilder();
        }
        CompletableFuture<AuditBuilder> future = createAuditParamAsync();
        // 同步等待结果，返回AuditParam
        return future.join();
    }

    /**
     * 通过配置和请求上下文填充AuditParam
     *
     * @param attrs 请求上下文
     * @return
     */
    private static AuditBuilder fillAuditBuilder(ServletRequestAttributes attrs, AuditBuilder builder) {
        builder.companyCode(auditProperties.getCompanyCode()) // 配置中获取
                .appId(auditProperties.getAppId()) // 配置中获取
                .functionModule(auditProperties.getFunctionModule()) // 可用户配置和构造，非必填
                .operationTime(getNowTime()) // 自动生成当前时间
                .operationUserType(auditProperties.getOperationUserType()) // 非必填
                .sourceChannel(auditProperties.getSourceChannel()) // 非必填
                .serverIp(getLocalIP()); //自动生成
        if (attrs == null) {
            logger.error("未获取到请求对象: ServletRequestAttributes is null");
            return builder;
        }
        HttpServletRequest request = attrs.getRequest();

        builder.operationUser(request.getRemoteUser() == null ? null : request.getRemoteUser())
                .srcIp(getSrcIp(request))
                .operationUserid(request.getRemoteUser() == null ? null : request.getRemoteUser())
                .recordUrl(request.getRequestURI());
        return builder;
    }

    /**
     * 开启线程池异步任务
     *
     * @return 返回CompletableFuture<AuditParam>
     */
    @Async("auditExecutor")
    private static CompletableFuture<AuditBuilder> createAuditParamAsync() {
        // 上下文无法在异步代码块中获取
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return CompletableFuture.supplyAsync(() -> {
            // 配置中填充参数
            return fillAuditBuilder(attrs, new AuditBuilder());
        });
    }

    /**
     * 获取客户端IP地址
     *
     * @param request
     * @return String
     */
    private static String getSrcIp(HttpServletRequest request) {
        // 获取客户端的真实IP
        // 注意：这可能会因为代理服务器或负载均衡器的存在而需要额外的处理
        String clientIP = request.getHeader("X-Forwarded-For");
        if (clientIP == null || clientIP.length() == 0 || "unknown".equalsIgnoreCase(clientIP)) {
            clientIP = request.getHeader("Proxy-Client-IP");
        }
        if (clientIP == null || clientIP.length() == 0 || "unknown".equalsIgnoreCase(clientIP)) {
            clientIP = request.getHeader("WL-Proxy-Client-IP");
        }
        if (clientIP == null || clientIP.length() == 0 || "unknown".equalsIgnoreCase(clientIP)) {
            clientIP = request.getRemoteAddr();
        }
        return clientIP;
    }

    /**
     * 当前时间 YYYY-mm-DD HH:mm:ss
     */
    private static String getNowTime() {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        return now.format(formatter);
    }

    /**
     * 获取本地IP（IPv4）
     *
     * @return 返回本地IPv4地址的字符串，如果没有找到则返回null
     */
    private static String getLocalIP() {
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface iface = interfaces.nextElement();
                if (!iface.isLoopback() && iface.isUp()) {
                    Enumeration<InetAddress> addresses = iface.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        InetAddress addr = addresses.nextElement();
                        if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
                            // 找到了一个非回环地址并且是IPv4
                            return addr.getHostAddress();
                        }
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
            return null;
        }
        // 如果没有找到IPv4地址，则返回null
        return null;
    }


    public static class AuditBuilder {
        private String companyCode;
        private String appId;
        private String functionModule;
        private String operationTime;
        private String operationUser;
        private String operationUserType;
        private String operationUserid;
        private String userid;
        private SourceChannel sourceChannel;
        private String srcIp;
        private String serverIp;
        private OperationType operationType;
        private Object operationObject;
        private OperationResult operationResult;
        private String failReason;
        private Integer recordCounts;
        private String recordType;
        private String recordUrl;
        private Integer recordPage;
        private Integer recordPageCounts;

        public AuditBuilder AuditParam(AuditParam auditParam) {
            this.companyCode = auditParam.getCompanyCode();
            this.appId = auditParam.getAppId();
            this.functionModule = auditParam.getFunctionModule();
            this.operationTime = auditParam.getOperationTime();
            this.operationUser = auditParam.getOperationUser();
            this.operationUserType = auditParam.getOperationUserType();
            this.operationUserid = auditParam.getOperationUserid();
            this.userid = auditParam.getUserid();
            this.sourceChannel = auditParam.getSourceChannel();
            this.srcIp = auditParam.getSrcIp();
            this.serverIp = auditParam.getServerIp();
            this.operationType = auditParam.getOperationType();
            // 应实现深拷贝以避免潜在的信息泄露或数据篡改风险
            this.operationObject = auditParam.getOperationObject();
            this.operationResult = auditParam.getOperationResult();
            this.failReason = auditParam.getFailReason();
            this.recordCounts = auditParam.getRecordCounts();
            this.recordType = auditParam.getRecordType();
            this.recordUrl = auditParam.getRecordUrl();
            this.recordPage = auditParam.getRecordPage();
            this.recordPageCounts = auditParam.getRecordPageCounts();
            return this;
        }

        public AuditBuilder companyCode(String companyCode) {
            this.companyCode = companyCode;
            return this;
        }

        public AuditBuilder appId(String appId) {
            this.appId = appId;
            return this;
        }

        public AuditBuilder functionModule(String functionModule) {
            this.functionModule = functionModule;
            return this;
        }

        public AuditBuilder operationTime(String operationTime) {
            this.operationTime = operationTime;
            return this;
        }

        public AuditBuilder operationUser(String operationUser) {
            this.operationUser = operationUser;
            return this;
        }

        public AuditBuilder operationUserType(String operationUserType) {
            this.operationUserType = operationUserType;
            return this;
        }

        public AuditBuilder operationUserid(String operationUserid) {
            this.operationUserid = operationUserid;
            return this;
        }

        public AuditBuilder userid(String userid) {
            this.userid = userid;
            return this;
        }

        public AuditBuilder sourceChannel(SourceChannel sourceChannel) {
            this.sourceChannel = sourceChannel;
            return this;
        }

        public AuditBuilder srcIp(String srcIp) {
            this.srcIp = srcIp;
            return this;
        }

        public AuditBuilder serverIp(String serverIp) {
            this.serverIp = serverIp;
            return this;
        }

        public AuditBuilder operationType(OperationType operationType) {
            this.operationType = operationType;
            return this;
        }

        public AuditBuilder operationObject(Object operationObject) {
            this.operationObject = operationObject;
            return this;
        }

        public AuditBuilder operationResult(OperationResult operationResult) {
            this.operationResult = operationResult;
            return this;
        }

        public AuditBuilder failReason(String failReason) {
            this.failReason = failReason;
            return this;
        }

        public AuditBuilder recordCounts(Integer recordCounts) {
            this.recordCounts = recordCounts;
            return this;
        }

        public AuditBuilder recordType(String recordType) {
            this.recordType = recordType;
            return this;
        }

        public AuditBuilder recordUrl(String recordUrl) {
            this.recordUrl = recordUrl;
            return this;
        }

        public AuditBuilder recordPage(Integer recordPage) {
            this.recordPage = recordPage;
            return this;
        }

        public AuditBuilder recordPageCounts(Integer recordPageCounts) {
            this.recordPageCounts = recordPageCounts;
            return this;
        }

        public AuditBuilder() {
        }

        public AuditParam build() {
            return new AuditParam(this);
        }
    }

    public String getCompanyCode() {
        return companyCode;
    }

    public void setCompanyCode(String companyCode) {
        this.companyCode = companyCode;
    }

    public Object getOperationObject() {
        return operationObject;
    }

    public void setOperationObject(Object operationObject) {
        this.operationObject = operationObject;
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getFunctionModule() {
        return functionModule;
    }

    public void setFunctionModule(String functionModule) {
        this.functionModule = functionModule;
    }

    public String getOperationTime() {
        return operationTime;
    }

    public void setOperationTime(String operationTime) {
        this.operationTime = operationTime;
    }

    public String getOperationUser() {
        return operationUser;
    }

    public void setOperationUser(String operationUser) {
        this.operationUser = operationUser;
    }

    public String getOperationUserType() {
        return operationUserType;
    }

    public void setOperationUserType(String operationUserType) {
        this.operationUserType = operationUserType;
    }

    public String getOperationUserid() {
        return operationUserid;
    }

    public void setOperationUserid(String operationUserid) {
        this.operationUserid = operationUserid;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    public SourceChannel getSourceChannel() {
        return sourceChannel;
    }

    public void setSourceChannel(SourceChannel sourceChannel) {
        this.sourceChannel = sourceChannel;
    }

    public String getSrcIp() {
        return srcIp;
    }

    public void setSrcIp(String srcIp) {
        this.srcIp = srcIp;
    }

    public String getServerIp() {
        return serverIp;
    }

    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
    }

    public OperationType getOperationType() {
        return operationType;
    }

    public void setOperationType(OperationType operationType) {
        this.operationType = operationType;
    }

    public OperationResult getOperationResult() {
        return operationResult;
    }

    public void setOperationResult(OperationResult operationResult) {
        this.operationResult = operationResult;
    }

    public String getFailReason() {
        return failReason;
    }

    public void setFailReason(String failReason) {
        this.failReason = failReason;
    }

    public Integer getRecordCounts() {
        return recordCounts;
    }

    public void setRecordCounts(Integer recordCounts) {
        this.recordCounts = recordCounts;
    }

    public String getRecordType() {
        return recordType;
    }

    public void setRecordType(String recordType) {
        this.recordType = recordType;
    }

    public String getRecordUrl() {
        return recordUrl;
    }

    public void setRecordUrl(String recordUrl) {
        this.recordUrl = recordUrl;
    }

    public Integer getRecordPage() {
        return recordPage;
    }

    public void setRecordPage(Integer recordPage) {
        this.recordPage = recordPage;
    }

    public Integer getRecordPageCounts() {
        return recordPageCounts;
    }

    public void setRecordPageCounts(Integer recordPageCounts) {
        this.recordPageCounts = recordPageCounts;
    }
}
